package com.soccerlover9527.jadis.datasturct.impl;

import com.soccerlover9527.jadis.datasturct.AbstractJadisObject;
import com.soccerlover9527.jadis.datasturct.JList;
import com.soccerlover9527.jadis.enums.DataType;
import com.soccerlover9527.jadis.enums.ListInsertPosition;
import com.soccerlover9527.jadis.exception.OperationNotSupportException;

import java.util.*;
import java.util.function.Consumer;

/**
 * <p></p>
 *
 * @author soccerlover9527@gmail.com
 * @since 2023-08-04
 */
public class JadisList extends AbstractJadisObject implements JList {
    /*
     * todo quick list
    private static final int DEFAULT_NODE_ARRAY_LENGTH = 8;
    private volatile int modCount = 0;
    */

    private final JLinkedList list = new JLinkedList();

    public JadisList() {
        super(DataType.LIST);
    }

    @Override
    public int length() {
        return list.size();
    }

    @Override
    public String leftPop() {
        return list.pop();
    }

    @Override
    public String rightPop() {
        return list.pollLast();
    }

    @Override
    public String indexOf(int index) {
        index = revertNegativeIndex(index);
        return list.get(index);
    }

    @Override
    public int leftPush(String value) {
        list.addFirst(value);
        return list.size();
    }

    @Override
    public int rightPush(String value) {
        list.addLast(value);
        return list.size();
    }

    @Override
    public List<String> range(int start, int end) {
        if (start > list.size() - 1) {
            return Collections.emptyList();
        }
        if (end < 0) {
            end = list.size() - end;
        }
        if (start > end) {
            return Collections.emptyList();
        }
        if (end > list.size() - 1) {
            end = list.size() - 1;
        }
        ArrayList<String> res = new ArrayList<>(end - start + 1);
        Iterator<String> iterator = list.iterator();
        for (int i = 0; i < end; i++) {
            if (i <= start) {
                iterator.next();
                continue;
            }
            res.add(iterator.next());
        }
        return res;
    }

    @Override
    public int insert(ListInsertPosition position, String pivot, String value) {
        switch (position) {
            case BEFORE -> {
                return list.insertBefore(pivot, value);
            }
            case AFTER -> {
                return list.insertAfter(pivot, value);
            }
            default -> throw new OperationNotSupportException("not support insert position:" + position);
        }
    }

    @Override
    public int remove(int count, String value) {
        return list.remove(count, value);
    }

    @Override
    public void set(int index, String value) {
        index = revertNegativeIndex(index);
        list.set(index, value);
    }

    @Override
    public void trim(int start, int end) {
        list.trim(start, end);
    }

    /**
     * revert negative index to the index which count from begin to end;
     *
     * @param index index
     * @return reverted index
     */
    private int revertNegativeIndex(int index) {
        return index >= 0 ? index : list.size + index;
    }

    private static class JLinkedList extends AbstractSequentialList<String>
            implements List<String>, Deque<String> {
        int size;
        Node<String> first;
        Node<String> last;

        public int insertAfter(String pivot, String value) {
            var index = first;
            if (index == null) {
                return -1;
            }
            while (index != null) {
                if (Objects.equals(pivot, index.item)) {
                    final var next = index.next;
                    var newNode = new Node<>(index, value, next);
                    if (next == null) {
                        last = newNode;
                    } else {
                        next.prev = newNode;
                    }
                    index.next = newNode;
                    size++;
                    return size;
                }
                index = index.next;
            }
            return -1;
        }

        public int insertBefore(String pivot, String value) {
            var index = first;
            if (index == null) {
                return -1;
            }
            while (index != null) {
                if (Objects.equals(pivot, index.item)) {
                    final var prev = index.prev;
                    var newNode = new Node<>(prev, value, index);
                    if (prev == null) {
                        first = newNode;
                    } else {
                        prev.next = newNode;
                    }
                    index.prev = newNode;
                    size++;
                    return size;
                }
                index = index.next;
            }
            return -1;
        }

        @Override
        public String get(int index) {
            checkElementIndex(index);
            return node(index).item;
        }

        @Override
        public void addFirst(String s) {
            final var f = first;
            final var newNode = new Node<>(null, s, f);
            first = newNode;
            if (f == null)
                last = newNode;
            else
                f.prev = newNode;
            size++;
            modCount++;
        }

        @Override
        public void addLast(String s) {
            final var l = last;
            final var newNode = new Node<>(l, s, null);
            last = newNode;
            if (l == null)
                first = newNode;
            else
                l.next = newNode;
            size++;
            modCount++;
        }

        @Override
        public boolean offerFirst(String s) {
            addFirst(s);
            return true;
        }

        @Override
        public boolean offerLast(String s) {
            addLast(s);
            return true;
        }

        @Override
        public String removeFirst() {
            final var f = first;
            var item = f.item;
            var next = f.next;
            //  help gc
            f.item = null;
            f.next = null;
            first = next;
            if (next == null)
                last = null;
            else
                next.prev = null;
            size--;
            modCount++;
            return item;
        }

        @Override
        public String removeLast() {
            final var l = last;
            var item = l.item;
            var prev = l.prev;
            //  help gc
            l.item = null;
            l.prev = null;
            last = prev;
            if (prev == null)
                first = null;
            else
                prev.next = null;
            size--;
            modCount++;
            return item;
        }

        @Override
        public String pollFirst() {
            return first == null ? null : removeFirst();
        }

        @Override
        public String pollLast() {
            return last == null ? null : removeLast();
        }

        @Override
        public String getFirst() {
            if (first == null)
                throw new NoSuchElementException();
            return first.item;
        }

        @Override
        public String getLast() {
            if (last == null)
                throw new NoSuchElementException();
            return last.item;
        }

        @Override
        public String peekFirst() {
            return first == null ? null : first.item;
        }

        @Override
        public String peekLast() {
            return last == null ? null : last.item;
        }

        @Override
        public boolean removeFirstOccurrence(Object o) {
            return remove(o);
        }

        @Override
        public boolean removeLastOccurrence(Object o) {
            var index = last;
            while (index != null) {
                if (Objects.equals(index.item, o)) {
                    unlink(index);
                    return true;
                }
                index = index.prev;
            }
            return false;
        }

        @Override
        public boolean offer(String s) {
            addLast(s);
            return true;
        }

        @Override
        public String set(int index, String element) {
            checkElementIndex(index);
            Node<String> node = node(index);
            var old = node.item;
            node.item = element;
            return old;
        }

        public void trim(int start, int end) {
            if (start > end || start > size - 1) {
                //  reset list
                first = null;
                last = null;
                size = 0;
            }
            if (size == 0) {
                return;
            }
            //  handle input param start and end.
            if (end > size - 1) {
                end = size - 1;
            }
            if (start < 0) {
                start = 0;
            }
            //  trim first index
            var index = first;
            for (var i = 0; i < start; i++) {
                index = index.next;
            }
            index.prev = null;
            first = index;
            //  trim end index
            index = last;
            for (var i = end; i < start; i++) {
                index = index.prev;
            }
            last.next = null;
            last = index;
            var trimCount = start + size - 1 - end;
            size = size - trimCount;
        }

        @Override
        public String remove() {
            return first == null ? null : removeFirst();
        }

        @Override
        public String remove(int index) {
            checkElementIndex(index);
            return unlink(node(index));
        }

        @Override
        public boolean remove(Object str) {
            var index = first;
            while (index != null) {
                if (Objects.equals(index.item, str)) {
                    unlink(index);
                    return true;
                }
                index = index.next;
            }
            return false;
        }

        /**
         * remove count N value in list.
         * count > 0: remove elements with value from beginning to end
         * count < 0: remove elements with value from end to head
         * count = 0: remove elements with input value
         *
         * @param count delete count
         * @param value deleted value
         * @return real delete count. return 0 if the
         */
        public int remove(int count, String value) {
            if (size == 0) {
                return 0;
            }
            Node<String> index;
            int realDelCount = 0;
            if (count > 0) {
                index = first;
                while (index != null && realDelCount < count) {
                    if (Objects.equals(index.item, value)) {
                        var temp = index.next;
                        unlink(index);
                        index = temp;
                        realDelCount++;
                    } else {
                        index = index.next;
                    }
                }
            } else {
                count = -count;
                index = last;
                //  remove all item if count == 0.
                while (index != null && (realDelCount < count || count == 0)) {
                    if (Objects.equals(index.item, value)) {
                        var temp = index.prev;
                        unlink(index);
                        index = temp;
                        realDelCount++;
                    } else {
                        index = index.prev;
                    }
                }
            }
            return realDelCount;
        }

        @Override
        public String poll() {
            return removeFirst();
        }

        @Override
        public String element() {
            return getFirst();
        }

        @Override
        public String peek() {
            return peekFirst();
        }

        @Override
        public void push(String s) {
            addFirst(s);
        }

        @Override
        public String pop() {
            return first == null ? null : removeFirst();
        }

        @Override
        public int size() {
            return size;
        }

        void linkBefore(String e, Node<String> node) {
            final var pred = node.prev;
            final var newNode = new Node<>(pred, e, node);
            node.prev = newNode;
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            size++;
            modCount++;
        }

        private void checkPositionIndex(int index) {
            if (!isPositionIndex(index))
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

        private boolean isPositionIndex(int index) {
            return index >= 0 && index <= size;
        }

        @Override
        public Iterator<String> descendingIterator() {
            return new DescendingIterator();
        }

        @Override
        public ListIterator<String> listIterator(int index) {
            checkPositionIndex(index);
            return new ListItr(index);
        }

        private class DescendingIterator implements Iterator<String> {
            private final ListItr itr = new ListItr(size());

            public boolean hasNext() {
                return itr.hasPrevious();
            }

            public String next() {
                return itr.previous();
            }

            public void remove() {
                itr.remove();
            }
        }

        private class ListItr implements ListIterator<String> {
            private Node<String> lastReturned;
            private Node<String> next;
            private int nextIndex;
            private int expectedModCount = modCount;

            ListItr(int index) {
                next = (index == size) ? null : node(index);
                nextIndex = index;
            }

            public boolean hasNext() {
                return nextIndex < size;
            }

            public String next() {
                checkForComodification();
                if (!hasNext())
                    throw new NoSuchElementException();

                lastReturned = next;
                next = next.next;
                nextIndex++;
                return lastReturned.item;
            }

            public boolean hasPrevious() {
                return nextIndex > 0;
            }

            public String previous() {
                checkForComodification();
                if (!hasPrevious())
                    throw new NoSuchElementException();

                lastReturned = next = (next == null) ? last : next.prev;
                nextIndex--;
                return lastReturned.item;
            }

            public int nextIndex() {
                return nextIndex;
            }

            public int previousIndex() {
                return nextIndex - 1;
            }

            public void remove() {
                checkForComodification();
                if (lastReturned == null)
                    throw new IllegalStateException();

                Node<String> lastNext = lastReturned.next;
                unlink(lastReturned);
                if (next == lastReturned)
                    next = lastNext;
                else
                    nextIndex--;
                lastReturned = null;
                expectedModCount++;
            }

            public void set(String e) {
                if (lastReturned == null)
                    throw new IllegalStateException();
                checkForComodification();
                lastReturned.item = e;
            }

            public void add(String e) {
                checkForComodification();
                lastReturned = null;
                if (next == null)
                    addLast(e);
                else
                    linkBefore(e, next);
                nextIndex++;
                expectedModCount++;
            }

            public void forEachRemaining(Consumer<? super String> action) {
                Objects.requireNonNull(action);
                while (modCount == expectedModCount && nextIndex < size) {
                    action.accept(next.item);
                    lastReturned = next;
                    next = next.next;
                    nextIndex++;
                }
                checkForComodification();
            }

            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }

        /**
         * Returns the (non-null) Node at the specified element index.
         */
        Node<String> node(int index) {
            Node<String> x;
            if (index < (size >> 1)) {
                x = first;
                for (int i = 0; i < index; i++)
                    x = x.next;
            } else {
                x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
            }
            return x;
        }

        private void checkElementIndex(int index) {
            if (!isElementIndex(index))
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

        private String outOfBoundsMsg(int index) {
            return "Index: " + index + ", Size: " + size;
        }

        private boolean isElementIndex(int index) {
            return index >= 0 && index < size;
        }

        String unlink(Node<String> node) {
            final var item = node.item;
            final var prev = node.prev;
            final var next = node.next;

            if (prev == null) {
                first = next;
            } else {
                prev.next = next;
                node.prev = null;
            }

            if (next == null) {
                last = prev;
            } else {
                next.prev = prev;
                node.next = null;
            }
            node.item = null;
            size--;
            modCount++;
            return item;
        }

        private static class Node<E> {
            E item;
            Node<E> next;
            Node<E> prev;

            Node(Node<E> prev, E element, Node<E> next) {
                this.item = element;
                this.next = next;
                this.prev = prev;
            }
        }
    }
}
